unit Voxel;

interface

uses
   Forms, Classes, OpenGL;

const
   VTransParent = 256;
   NormalCountTS = 36;
   NormalCountRA2 = 241;

type
   EError = (OK, ReadFailed, WriteFailed, InvalidSpanDataSizeCalced, InvalidSpan,
     BadSpan_SecondVoxelCount, Unhandled_Exception);

   ESpectrumMode = (smColors, smNormals);
   EModelViewMode = (vmFull, vmEmphasiseDepth, vmCrossSection);

   EModelViewOrient = (voTop, voRight, voBack, voBottom, voLeft, voFront);
   TGLMatrixf4 = array[0..3, 0..3] of Single;

   TVoxelHeader = packed record
      FileType: packed array[1..16] of Char; // always "Voxel Animation"
      Unknown, // always 1
      NumSections,
      NumSections2,
      BodySize: LongInt; // as above
      StartPaletteRemap,
      EndPaletteRemap: Byte; // Color indexes
      PaletteData: packed array[1..256*3] of Byte; // never used
   end;

   TVoxelSectionHeader = packed record
      Name: array[1..16] of Char; // ASCIIZ
      Number, // ordinal
      Unknown1, // always 1
      Unknown2: LongInt; // always 2
   end;

   TVoxelSectionTailer = packed record
      SpanStartOfs,
      SpanEndOfs,
      SpanDataOfs: LongInt;
      Det: Single;
      Transform: packed array[1..3,1..4] of Single;
      MinBounds, MaxBounds: packed array[1..3] of Single;
      XSize,
      YSize,
      ZSize,
      Unknown: Byte; // always 2 (or 4?); possibly normals-encoding scheme selection
   end;

   TVoxel = record
      Color: Byte;
      Normal: Byte;
      Used: Boolean;
   end;

   TThumbnail = record
      Width, Height: Integer;
   end;

   TViewport = record
      Left, Top, Zoom: Integer;
      hasBeenUsed: Boolean; // this flag lets the ui know if this
                   // view has been used already (so zoom is a user setting)
   end;

   TVoxelSection = class
   private
      spectrum: ESpectrumMode;
      // for storing / accessing the stored data
      procedure SetDataSize(XSize,YSize,ZSize: Integer);
      procedure DefaultTransforms;
      // function PackVoxel(Unpacked: TVoxel): TVoxelPacked;
      // procedure UnpackVoxel(PackedVoxel: TVoxelPacked; var dest: TVoxel);
   public
      Data: array of array of array of TVoxel; // as is 32-bit type, should be packed anyway
      MaxNormal, // the highest normal value
      X, Y, Z: Integer; // cursor location // TODO: Cursor location should not be a property of a voxel section! Poor code!!!
      // other
      Header: TVoxelSectionHeader;
      Tailer: TVoxelSectionTailer;
      constructor Create; overload;
      constructor Create(Name: string; Number, XSize,YSize,ZSize: Integer); overload;
      destructor Destroy; override;
      procedure Resize(XSize,YSize,ZSize: Integer);
      //blow-up tool for content resizing
      //procedure ResizeBlowUp(Scale: Integer);
      procedure Clear; // blanks the entire voxel model
      procedure SetVoxel(x,y,z: Integer; src: TVoxel);
      procedure GetVoxel(x,y,z: Integer; var dest: TVoxel);
      function GetVoxelSafe(x,y,z: Integer; var dest: TVoxel): Boolean;
      // loading and saving
      function LoadFromFile(var F: File; HeadOfs, BodyOfs, TailOfs : Integer): EError;
//    function SaveToFileHeader(var F: File): EError;
      function SaveToFileBody(var F: File): EError;
 //   function SaveToFileTailer(var F: File): EError;
      // utility methods
      function Name: string;
      // new by Koen
      procedure SetHeaderName(Name: String);

      function IsVoxelEmpty(X, Y, Z: Integer): Boolean;
      
      //New undo system by Koen - SaveUndoDump - saves data to an undo stream
      //procedure SaveUndoDump(fs: TStream);
      //loads data of this voxel section from a stream
      //procedure LoadUndoDump(fs: TStream);

      //a directional and a positional vector (x,y,z)=PosVector+t*DirectionVector
      //procedure FlipMatrix(VectorDir, VectorPos: Array of Single; Multiply: Boolean=True);
      procedure Assign(const _VoxelSection : TVoxelSection);
      function GetTransformAsOpenGLMatrix : TGlmatrixf4;
   end;
   PVoxelSection = ^TVoxelSection;

   TVoxelModel = class
   public
      Loaded: Boolean;
      ErrorCode: EError; // if an error, says what it is
      ErrorMsg, // if an error, says what it is
      Filename: string;
      Header: TVoxelHeader;
      Section: array of TVoxelSection;
      constructor Create;
      destructor Destroy; override;
      procedure LoadFromFile(Fname: string); // examine ErrorCode for success
      procedure SaveToFile(Fname: string); // examine ErrorCode for success
      function isOpen: Boolean;
      procedure InsertSection(SectionIndex: Integer; Name: String; XSize,YSize,ZSize: Integer);
      procedure RemoveSection(SectionIndex: Integer);
   end;

   function AsciizToStr(var src: array of Char; maxlen: Byte): string;

implementation

uses
   SysUtils, Math, Dialogs;

function AsciizToStr(var src: array of Char; maxlen: Byte): string;
var
   ch: Char;
   i: Integer;
begin
   SetLength(Result,maxlen);
   for i := 1 to maxlen do
   begin
      ch := src[i-1];
      if Ord(ch) = 0 then
      begin
         SetLength(Result,i-1);
         Break;
      end;
      Result[i] := ch;
   end;
end;

procedure DebugMsg(Msg: string; Typ: TMsgDlgType);
begin
   // in this version, just show a messsage box for the user
   if (MessageDlg('Debug:' + Chr(10) + Msg,Typ,[mbOK,mbCancel],0) > 0) then
      Halt;
end;

(*** TVoxelSection members *********************************************)

// Min/MaxBounds Fixed! - Stucuk
// Min/MaxBounds Completely Fixed! - HBD

procedure TVoxelSection.DefaultTransforms;
var
   i, j: integer;
   // ---- x,y,z : single;
begin
   Tailer.Det := 1;

   for i := 1 to 3 do
      for j := 1 to 3 do // from memory, don't think this transform is ever used.
         if i = j then
            Tailer.Transform[i,j] := 1
         else
            Tailer.Transform[i,j] := 0;

 { X := Tailer.MaxBounds[1] - (Tailer.MaxBounds[1]-Tailer.MinBounds[1]/2);
   Y := Tailer.MaxBounds[2] - (Tailer.MaxBounds[2]-Tailer.MinBounds[2]/2);
   Z := Tailer.MaxBounds[3] - (Tailer.MaxBounds[3]-Tailer.MinBounds[3]/2); }

  with Tailer do
  begin
    MaxBounds[1] := XSize shr 1;
    MaxBounds[2] := YSize shr 1;
    MaxBounds[3] := ZSize;
    // MaxBounds[3] := ZSize shr 1;
    MinBounds[1] := MaxBounds[1] - XSize;
    MinBounds[2] := MaxBounds[2] - YSize;
    MinBounds[3] := 0;
    // MinBounds[3] := MaxBounds[3] - ZSize;
  end;
end;

procedure TVoxelSection.Resize(XSize,YSize,ZSize: Integer);
begin
   // memory alloc
   SetDataSize(XSize,YSize,ZSize); // will preserve contents where possible I believe
   // set tailer
   Tailer.XSize := XSize;
   Tailer.YSize := YSize;
   Tailer.ZSize := ZSize;
   DefaultTransforms;
   // and (re)create views
end;

function TVoxelSection.GetTransformAsOpenGLMatrix : TGLmatrixf4;
begin
   Result[0,0] := Tailer.Transform[1,1];
   Result[0,1] := Tailer.Transform[2,1];
   Result[0,2] := Tailer.Transform[3,1];
   Result[0,3] := 0;

   Result[1,0] := Tailer.Transform[1,2];
   Result[1,1] := Tailer.Transform[2,2];
   Result[1,2] := Tailer.Transform[3,2];
   Result[1,3] := 0;

   Result[2,0] := Tailer.Transform[1,3];
   Result[2,1] := Tailer.Transform[2,3];
   Result[2,2] := Tailer.Transform[3,3];
   Result[2,3] := 0;

   Result[3,0] := Tailer.Transform[1,4];
   Result[3,1] := Tailer.Transform[2,4];
   Result[3,2] := Tailer.Transform[3,4];
   Result[3,3] := 1;
end;

procedure TVoxelSection.SetHeaderName(Name: String);
const
   MAX_LEN = 15;
var
   i: integer;
begin
   for i:=1 to 16 do
      Header.Name[i]:=#0;
   for i := 1 to Length(Name) do
   begin
      if i > MAX_LEN then break;
      Header.Name[i] := Name[i];
   end;
end;

constructor TVoxelSection.Create(Name: string; Number, XSize,YSize,ZSize: Integer);
begin
   // allocate the memory
   SetDataSize(XSize,YSize,ZSize);
   // create header
   SetHeaderName(Name);
   Header.Number := Number;
   Header.Unknown1 := 1; // TODO: review if this is correct in all cases etc
   Header.Unknown2 := 2; // TODO: review if this is correct in all cases etc
   // create tailer
   Tailer.XSize := XSize;
   Tailer.YSize := YSize;
   Tailer.ZSize := ZSize;
   // TODO: FIX THIS! TVoxelSection.Tailer.Unknown is not properly initialized
   Tailer.Unknown := 2; // or 4 in RA2?  TODO: review if this is correct in all cases etc

   Tailer.Det:=1/12; //oops, forgot to set Det part correctly

   Tailer.MaxBounds[1] := 0;
   Tailer.MaxBounds[2] := 0;
   Tailer.MaxBounds[3] := 0;

   Tailer.MinBounds[1] := 0;
   Tailer.MinBounds[2] := 0;
   Tailer.MinBounds[3] := 0;

   DefaultTransforms;
   // clear it
   Clear;
end;

procedure TVoxelSection.Assign(const _VoxelSection : TVoxelSection);
var
   i,x,y,z : integer;
begin
   // Copy Header.
   for i := Low(Header.Name) to High(Header.Name) do
      Header.Name[i] := _VoxelSection.Header.Name[i];
   Header.Number := _VoxelSection.Header.Number;
   Header.Unknown1 := _VoxelSection.Header.Unknown1;
   Header.Unknown2 := _VoxelSection.Header.Unknown2;
   // Copy Tailer.
   Tailer.SpanStartOfs := _VoxelSection.Tailer.SpanStartOfs;
   Tailer.SpanEndOfs := _VoxelSection.Tailer.SpanEndOfs;
   Tailer.SpanDataOfs := _VoxelSection.Tailer.SpanDataOfs;
   Tailer.Det := _VoxelSection.Tailer.Det;
   for i := 1 to 3 do
   begin
      Tailer.Transform[i,1] := _VoxelSection.Tailer.Transform[i,1];
      Tailer.Transform[i,2] := _VoxelSection.Tailer.Transform[i,2];
      Tailer.Transform[i,3] := _VoxelSection.Tailer.Transform[i,3];
      Tailer.Transform[i,4] := _VoxelSection.Tailer.Transform[i,4];
   end;
   for i := 1 to 3 do
   begin
      Tailer.MinBounds[i] := _VoxelSection.Tailer.MinBounds[i];
      Tailer.MaxBounds[i] := _VoxelSection.Tailer.MaxBounds[i];
   end;
   Tailer.XSize := _VoxelSection.Tailer.XSize;
   Tailer.YSize := _VoxelSection.Tailer.YSize;
   Tailer.ZSize := _VoxelSection.Tailer.ZSize;
   Tailer.Unknown := _VoxelSection.Tailer.Unknown;
   SetDataSize(Tailer.XSize,Tailer.YSize,Tailer.ZSize);
   // Copy Data
   for x := Low(Data) to High(Data) do
      for y := Low(Data[x]) to High(Data[x]) do
         for z := Low(Data[x,y]) to High(Data[x,y]) do
         begin
            Data[x,y,z] := _VoxelSection.Data[x,y,z];
         end;
   // Copy anything else.
   spectrum := _VoxelSection.spectrum;
   MaxNormal := _VoxelSection.MaxNormal;
 { X := _VoxelSection.X;
   Y := _VoxelSection.Y;
   Z := _VoxelSection.Z; }
end;

constructor TVoxelSection.Create; begin end;

destructor TVoxelSection.Destroy;
begin
   SetDataSize(0,0,0);
   inherited Destroy;
end;


function TVoxelSection.Name: string;
begin
   Result := AsciizToStr(Header.Name,16);
end;

{function TVoxelSection.PackVoxel(Unpacked: TVoxel): TVoxelPacked;
begin
   Result := Unpacked.Normal or (Unpacked.Color shl 8) or (Byte(Unpacked.Used) shl 16)
end;}

{procedure TVoxelSection.UnpackVoxel(PackedVoxel: TVoxelPacked; var dest: TVoxel);
begin
   dest.Normal := (PackedVoxel and $000000FF);
   dest.Color := (PackedVoxel and $0000FF00) shr 8;
   dest.Used :=   (PackedVoxel and $00010000) > 0;
end;}

procedure TVoxelSection.Clear; // blanks the entire voxel model
var
  x, y, z: Integer;
begin
  for x := 0 to (Tailer.XSize - 1) do
    for y := 0 to (Tailer.YSize - 1) do
      for z := 0 to (Tailer.ZSize - 1) do
      begin
        Data[x,y,z].Color := 0;
        Data[x,y,z].Normal := 0;
        Data[x,y,z].Used := False;
      end;
end;

procedure TVoxelSection.SetDataSize(XSize,YSize,ZSize: Integer);
var
   x, y: Integer;
begin
   // 1.71: Let's clear the memory from the stuff after XSize.
   if (High(Data) >= XSize) then
   begin
      for x := XSize to High(Data) do
      begin
         for y := 0 to High(Data[x]) do
         begin
            SetLength(Data[x,y],0);
         end;
      end;
   end;
   // Old code continues here.
   SetLength(Data,XSize);
   for x := 0 to (XSize - 1) do
   begin
      // 1.71: Let's clear the memory stuff after YSize
      if (High(Data[x]) >= YSize) then
      begin
         for y := YSize to High(Data[x]) do
         begin
            SetLength(Data[x,y],0);
         end;
      end;
      // Old code continues here.
      SetLength(Data[x],YSize);
      for y := 0 to (YSize - 1) do
         SetLength(Data[x,y],ZSize);
   end;
end;

// This catches the crashes!
{$RANGECHECKS ON}
procedure TVoxelSection.SetVoxel(x,y,z: Integer; src: TVoxel);
begin
  Data[x,y,z].Color := src.Color;
  Data[x,y,z].Normal := src.Normal;
  Data[x,y,z].Used := src.Used;
end;

procedure TVoxelSection.GetVoxel(X, Y, Z: Integer; var Dest: TVoxel);
begin
  Dest.Color := Data[X,Y,Z].Color;
  Dest.Normal := Data[X,Y,Z].Normal;
  Dest.Used := Data[X,Y,Z].Used;
end;

function TVoxelSection.GetVoxelSafe(x,y,z: Integer; var dest: TVoxel): Boolean;
begin
  Result := False;
  if (x >= 0) and (x < Tailer.XSize) and (y >= 0) and (y < Tailer.YSize) and (z >= 0) and (z < Tailer.ZSize) then
  begin
    GetVoxel(X, Y, Z, Dest);
    Result := True;
  end;
end;

function TVoxelSection.IsVoxelEmpty(X, Y, Z: Integer): Boolean;
begin
  if (X < 0) or (X >= Tailer.XSize) or
     (Y < 0) or (Y >= Tailer.YSize) or
     (Z < 0) or (Z >= Tailer.ZSize) then
    Result := True
  else Result := not Data[X,Y,Z].Used;
end;

// {$OPTIMIZATION OFF}
{$RANGECHECKS ON}

function TVoxelSection.LoadFromFile(var F: File; HeadOfs, BodyOfs, TailOfs : Integer): EError;
var
   BytesToRead,
   BytesRead,
   i, SpanCount, Ofs,
   x, y, z,
   voxel_num, num_voxels,
   SpanDataLen: Integer;
   UnpackedVoxel: TVoxel;
   SpanStart, SpanEnd: packed array of LongInt;
   SpanData: packed array of Byte; // the raw data
   procedure CleanUp(Err: Boolean);
   begin
      SetLength(SpanData,0);
      SetLength(SpanEnd,0);
      SetLength(SpanStart,0);
      if Err then
         SetDataSize(0,0,0);
   end;
begin
   MaxNormal := 0;
   try
      Result := OK; // assume ok
// read in tailer first
      Seek(F,TailOfs); // move to tail offset
      BytesToRead := SizeOf(TVoxelSectionTailer);
      BlockRead(F,Tailer,BytesToRead,BytesRead);
      if (BytesToRead <> BytesRead) then
      begin
         Result := ReadFailed;
         CleanUp(true);
         Exit;
      end;
// read in head next
      Seek(F,HeadOfs);
      BytesToRead := SizeOf(TVoxelSectionHeader);
      BlockRead(F,Header,BytesToRead,BytesRead);
      if (BytesToRead <> BytesRead) then
      begin
         Result := ReadFailed;
         CleanUp(True);
         Exit;
      end;
{$IFDEF DEBUG_NORMALS}
      DebugMsg('Header.Section name is : ' + Chr(10) + '[' + AsciizToStr(Header.Name,16) + ']' + Chr(10) +
        'Header.Number is : ' + IntToStr(Header.Number) + Chr(10) +
        'Header.Unknown1 is : ' + IntToStr(Header.Unknown1) + Chr(10) +
        'Header.Unknown2 is : ' + IntToStr(Header.Unknown2) + Chr(10) +
        'Tailer.Unknown is : ' + IntToStr(Tailer.Unknown)
        ,mtInformation);
{$ENDIF}
// create memory for storing the actual voxels; note the creation order
      with Tailer do
      begin
         SetDataSize(XSize,YSize,ZSize);
         Clear; // empty data
         SpanCount := XSize * YSize;
      end;
      SetLength(SpanStart,SpanCount);
      SetLength(SpanEnd,SpanCount);
      // read in the span start offsets
      Seek(F,BodyOfs + Tailer.SpanStartOfs);
      BytesToRead := SpanCount * SizeOf(LongInt);
      BlockRead(F,SpanStart[0],BytesToRead,BytesRead);
      if (BytesToRead <> BytesRead) then
      begin
         Result := ReadFailed;
         CleanUp(True);
         Exit;
      end;
      Seek(F,BodyOfs + Tailer.SpanEndOfs);
      BlockRead(F,SpanEnd[0],BytesToRead,BytesRead);
      if (BytesToRead <> BytesRead) then
      begin
         Result := ReadFailed;
         CleanUp(True);
         Exit;
      end;
      // and load the spans data into a buffer to be parsed
      // find last span end
      i := SpanCount - 1;
      while (SpanEnd[i] = -1) and (i > 0) do
         Dec(i);
      SpanDataLen := SpanEnd[i];
      // find first span end
      i := 0;
      while (SpanStart[i] = -1) and ((i+1) < SpanCount) do
         Inc(i);
      Dec(SpanDataLen,SpanStart[i]);
      Inc(SpanDataLen); // safety
      if SpanDataLen < 1 then
      begin
         Result := InvalidSpanDataSizeCalced;
         CleanUp(True);
         Exit;
      end;
      SetLength(SpanData,SpanDataLen);
      BlockRead(F,SpanData[0],SpanDataLen,BytesRead);
      if (SpanDataLen <> BytesRead) then
      begin
         Result := ReadFailed;
         CleanUp(True);
         Exit;
      end;
      // Banshee's adition here:
      // -- Speed up operations and avoid future mistakes.
      if Tailer.Unknown = 2 then
         MaxNormal := 35
      else if Tailer.Unknown = 4 then
         MaxNormal := 240;
      if Tailer.Det = 0 then
         Tailer.Det := 1;
      // -- End of Banshee's speed up operation.
      // and parse the spans
      i := -1; // will be inc'ed before anything else
      UnpackedVoxel.Used := True;
      for y := 0 to (Tailer.YSize - 1) do
      begin
         for x := 0 to (Tailer.XSize - 1) do
         begin
            Inc(i);
            if (SpanStart[i] = -1) or (SpanEnd[i] = -1) then // skip empty spans
               Continue;
            z := 0;
            Ofs := SpanStart[i];
            while (z < Tailer.ZSize) and (z < SpanEnd[i]) do
            begin
               // this is one z - span
               Inc(z,SpanData[Ofs]); Inc(Ofs); // skip count
               num_voxels := SpanData[Ofs]; Inc(Ofs);
               if (z >= Tailer.ZSize) then
                  Break;
               if (z + num_voxels > Tailer.ZSize) then
                  DebugMsg('Err',mtError);
               for voxel_num := 1 to num_voxels do
               begin
                  with UnpackedVoxel do
                  begin
                     Color := SpanData[Ofs]; Inc(Ofs);
                     Normal := SpanData[Ofs]; Inc(Ofs);
                     // Banshee: I've changed this, so it avoids
                     // invalid normal values to be placed when
                     // the file loads
                     if (Normal > MaxNormal) then
                        Normal := MaxNormal;
                  end;
                  SetVoxel(x,y,z,UnpackedVoxel);
                  Inc(z);
               end;
               // check second span number
               voxel_num := SpanData[Ofs]; Inc(Ofs);
               if voxel_num <> num_voxels then
               begin
                  Result := BadSpan_SecondVoxelCount;
                  CleanUp(True);
                  Exit;
               end;
            end;
         end;
      end;
   // done
   except
      Result := Unhandled_Exception;
      CleanUp(True);
   end;
   // done
   CleanUp(False);
end;

function TVoxelSection.SaveToFileBody(var F: File): EError;
var
  FSpanStart, FSpanEnd, FBody,
  SpanStart, SpanEnd: LongInt;
  x, y, z, SpanSize, s: integer;
  skip, spanlen, Color, normal: byte;

   function SpanUsed(x, y: integer): boolean;
   var
      z: integer;
   begin
      Result := True; // assume it is
      for z := 0 to (Tailer.ZSize - 1) do
         if Data[x,y,z].Used then // used flag set?
            Exit;
      Result := False; // if here, span wasn't used after all
   end;

   function SpanLength(x,y,z: integer): integer;
   begin
      Result := 0;
      while z < Tailer.ZSize do
      begin
         if Data[x,y,z].Used then
            Inc(Result)
         else
            Exit;
         Inc(z);
      end;
   end;

begin
   // work out offsets
   FBody := FilePos(F);
   SpanSize := Tailer.XSize * Tailer.YSize * SizeOf(LongInt);
   FSpanEnd := FBody - SpanSize;
   FSpanStart := FSpanEnd - SpanSize;
   // and save some info now to the tailer
   Tailer.SpanStartOfs := FSpanStart;
   Tailer.SpanEndOfs := FSpanEnd;
   Tailer.SpanDataOfs := FBody;
   for y := 0 to (Tailer.YSize - 1) do
      for x := 0 to (Tailer.XSize - 1) do
      begin
         if not SpanUsed(x,y) then
         begin
            SpanStart := -1;
            SpanEnd := -1;
         end
         else
         begin
            SpanStart := FilePos(F) - Tailer.SpanDataOfs;
            skip := 0;
            z := 0;
            while z < (Tailer.ZSize) do
            begin
               spanlen := SpanLength(x,y,z);
               if (spanlen > 0) then
               begin
                  BlockWrite(F,skip,1); // write skip
                  BlockWrite(F,spanlen,1); // write span length again
                  for s := 1 to spanlen do
                  begin
                     BlockWrite(F,Data[x,y,z+s-1].Color,1);
                     BlockWrite(F,Data[x,y,z+s-1].normal,1);
                  end;
                  BlockWrite(F,spanlen,1); // write span length again
                  Inc(z,spanlen);
                  skip := 0;
               end
               else
               begin
                  Inc(skip);
                  Inc(z);
               end;
            end;
            if skip > 0 then
            begin // write a dummy?
               BlockWrite(F,skip,1);
               spanlen := 0;
               BlockWrite(F,spanlen,1);
               BlockWrite(F,spanlen,1);
            end;
            SpanEnd := FilePos(F) - Tailer.SpanDataOfs - 1;
         end;
         // write span data
         FBody := FilePos(F);
         Seek(F,FSpanStart);
         BlockWrite(F,SpanStart,SizeOf(SpanStart));
         Inc(FSpanStart,SizeOf(SpanStart));
         Seek(F,FSpanEnd);
         BlockWrite(F,SpanEnd,SizeOf(SpanEnd));
         Inc(FSpanEnd,SizeOf(SpanEnd));
         Seek(F,FBody); //return to span data
      end;
   Result := OK;
end;

{$OPTIMIZATION ON}
{$RANGECHECKS OFF}

constructor TVoxelModel.Create;
begin
   ErrorCode := OK;
   Filename := '';
   Loaded := False;
end;

destructor TVoxelModel.Destroy;
var
   x : integer;
begin
   for x := High(Section) downto Low(Section) do
   begin
      RemoveSection(x);
   end;
   SetLength(Section,0);
   inherited Destroy;
end;

procedure TVoxelModel.LoadFromFile(FName: string);
var
   F: File;
   BytesToRead,
   BytesRead,
   i,
   HeadOfs, TailOfs, BodyOfs: Integer;
begin
     {DebugMsg(      'SizeOf(Header) = ' + IntToStr(SizeOf(Header)) + Chr(10) +
        'SizeOf(TVoxelSectionHeader) = ' + IntToStr(SizeOf(TVoxelSectionHeader)) + Chr(10) +
        'SizeOf(TVoxelSectionTailer) = ' + IntToStr(SizeOf(TVoxelSectionTailer)),mtInformation);}
   ErrorCode := OK;
   Loaded := False;
   try  // if file doesn't exist...
      Filename := FName;
      // open voxel file to read
      AssignFile(F,Filename);
      FileMode := fmOpenRead; // read only
      Reset(F,1); // file of byte
      // read in main header
      BytesToRead := SizeOf(Header);
      BlockRead(F,Header,BytesToRead,BytesRead);
      if (BytesRead <> BytesToRead) then
      begin
         ErrorCode := ReadFailed;
         Showmessage('Error: Could not read Voxel Header');
         CloseFile(F);
         Exit;
      end;
      //DebugMsg('there are ' + IntToStr(Header.NumSections) + ' sections',mtInformation);
      // read in sections
      // prepare section objects
      SetLength(Section,Header.NumSections);
      for i := 1 to Header.NumSections do
         Section[i-1] := TVoxelSection.Create;
      HeadOfs := SizeOf(Header); // starts immediately after header
      BodyOfs := HeadOfs + (Header.NumSections * SizeOf(TVoxelSectionHeader)); // after all section headers
      TailOfs := BodyOfs + Header.BodySize; // last part of file
      // and load them
      for i := 1 to Header.NumSections do
      begin
         ErrorCode := Section[i-1].LoadFromFile(F,HeadOfs,BodyOfs,TailOfs);
         if (ErrorCode <> OK) then
         begin
            ErrorMsg := 'Could not read Voxel Section Tailer[' + IntToStr(i) + ']';
            DebugMsg(ErrorMsg,mtError);
            Exit;
         end;
         Inc(HeadOfs,SizeOf(TVoxelSectionHeader)); // next header
         // BodyOfs doesn't change, as relative offset is in tailer
         Inc(TailOfs,SizeOf(TVoxelSectionTailer)); // next tailer
      end;
      Loaded := True;
      CloseFile(F);
   except on E : EInOutError do // VK 1.36 U
      MessageDlg('Error: ' + E.Message + Char($0A) + Filename, mtError, [mbOK], 0);
   end;
end;

procedure TVoxelModel.SaveToFile(Fname: string);
{ First, we create the file.  We write blanks to represent the
  header. We then write the section headers.  We then encode the
  section bodies.  We then return to the section headers and
  write them, then the section tailers.  We then write the voxel
  header.
}
var
   F: File;
   BytesToWrite,
   BytesWritten: integer;

   function WriteBlank(count: integer): EError;
   var
      i: integer;
      ch: byte;
   begin
      ch := 0;
      BytesToWrite := 1;
      for i := 1 to count do
      begin
         BlockWrite(F,ch,BytesToWrite,BytesWritten);
         if (BytesWritten <> BytesToWrite) then
         begin
            Result := WriteFailed;
            Exit;
         end;
      end;
      Result := OK;
   end;
var
   i, BodyStart, BodyEnd: Integer;
begin
   if not Loaded then Exit; // cannot save an empty voxel?
   //-- when we save an empty voxel - this voxel will be unsupported - we must change this [Kamil ^aka Plasmadroid]
   ErrorCode := OK;
   try
      Filename := Fname;
      // create voxel file to write
      AssignFile(F,Filename);
      FileMode := fmOpenWrite; // we save file, so write mode [VK]
      Rewrite(F,1); // file of byte
      // write main header (overwritten later)
      ErrorCode := WriteBlank(SizeOf(TVoxelHeader));
      if (ErrorCode <> OK) then
      begin
         ErrorMsg := 'Could not write Voxel Header (pass 2)';
         DebugMsg(ErrorMsg,mtError);
         Exit;
      end;
      // write section heads (real data)
      BytesToWrite := SizeOf(TVoxelSectionHeader);
      for i := 1 to Header.NumSections do
      begin
         BlockWrite(F,Section[Pred(i)].Header,BytesToWrite,BytesWritten);
         if (BytesWritten <> BytesToWrite) then
         begin
            ErrorCode := WriteFailed;
            ErrorMsg := 'Could not write Voxel Section Header #' + IntToStr(i) + '(pass 2)';
            DebugMsg(ErrorMsg,mtError);
            Exit;
         end;
      end;
      // write section bodies (real data)
      BodyStart := FilePos(F); // for later; could have calc'ed it instead
      for i := 0 to (Header.NumSections - 1) do
      begin
         with Section[i] do
         begin
            // first, write blank data where the span starts / ends will be
            ErrorCode := WriteBlank(Tailer.XSize*Tailer.YSize*4*2);
            if (ErrorCode <> OK) then
            begin
               ErrorMsg := 'Could not write Voxel Section Body #' + IntToStr(Succ(i)) + ' (pass 1)';
               DebugMsg(ErrorMsg,mtError);
               Exit;
            end;
            // now write the actual data
            ErrorCode := SaveToFileBody(F);
            if (ErrorCode <> OK) then
            begin
               ErrorMsg := 'Could not write Voxel Section Body #' + IntToStr(Succ(i)) + ' (pass 2)';
               DebugMsg(ErrorMsg,mtError);
               Exit;
            end;
         end;
      end;
      BodyEnd := FilePos(F); // for later; could have calc'ed it instead
      // write section tailers (real data)
      BytesToWrite := SizeOf(TVoxelSectionTailer);
      for i := 0 to (Header.NumSections - 1) do
      begin
         with Section[i].Tailer do
         begin
            Dec(SpanStartOfs,BodyStart);
            Dec(SpanEndOfs,BodyStart);
            Dec(SpanDataOfs,BodyStart);
         end;
         BlockWrite(F,Section[i].Tailer,BytesToWrite,BytesWritten);
         if (BytesWritten <> BytesToWrite) then
         begin
            ErrorCode := WriteFailed;
            ErrorMsg := 'Could not write Voxel Section Tailer #' + IntToStr(i) + '(pass 2)';
            DebugMsg(ErrorMsg,mtError);
            Exit;
         end;
      end;
      // write main header (real data)
      Seek(F,0); // go to start of file again
      Header.BodySize := BodyEnd - BodyStart;
      BytesToWrite := SizeOf(TVoxelHeader);
      BlockWrite(F,Header,BytesToWrite,BytesWritten);
      if (BytesWritten <> BytesToWrite) then
      begin
         ErrorMsg := 'Could not write Voxel Header (pass 2)';
         DebugMsg(ErrorMsg,mtError);
         Exit;
      end;
      CloseFile(F);
   except on E : EInOutError do // VK 1.36 U
		MessageDlg('Error: ' + E.Message + Char($0A) + Filename, mtError, [mbOK], 0);
   end;
end;

function TVoxelModel.isOpen: Boolean; begin Result := Loaded; end;

// Insert a new section with SectionIndex :)
procedure TVoxelModel.InsertSection(SectionIndex: Integer; Name: String; XSize,
  YSize, ZSize: Integer);
var
   i: Integer;
begin
   //SectionIndex contains the index of the *new* section to create...
   SetLength(Section,Header.NumSections+1);
   for i:=Header.NumSections-1 downto SectionIndex do
   begin
      Section[i+1]:=Section[i];
      Section[i+1].Header.Number:=i+1;
   end;
   Section[SectionIndex]:=TVoxelSection.Create(Name,SectionIndex,XSize,YSize,ZSize);
   Inc(Header.NumSections);
   Inc(Header.NumSections2);
end;

procedure TVoxelModel.RemoveSection(SectionIndex: Integer);
var
   i: Integer;
begin
   Section[SectionIndex].Free;
   for i:= SectionIndex to Header.NumSections - 2 do
   begin
      Section[i]:=Section[i+1];
      Section[i].Header.Number:=i;
   end;
   Dec(Header.NumSections);
   Dec(Header.NumSections2);
end;

//this function uses basic matrix/vector operations (nice hybrid Koen) to
//allow flipping and nudging.
{procedure TVoxelSection.FlipMatrix(VectorDir,
  VectorPos: array of Single; Multiply: Boolean=True);
var
   NewData: array of array of array of TVoxelPacked; // as is 32-bit type, should be packed anyway
   i,j,k,a,b,c: Integer;
   Empty: TVoxel;
   PackedVoxel: TVoxelPacked;
begin
   // prepare empty voxel
   with Empty do
   begin
      Color := 0;
      Normal := 0;
      Used := False;
   end;
   PackedVoxel := PackVoxel(Empty);

   //create new data matrix
   SetLength(NewData,Tailer.XSize,Tailer.YSize,Tailer.ZSize);
   //fill it with empty voxels
   for i:=0 to Tailer.XSize - 1 do
   begin
      for j:=0 to Tailer.YSize - 1 do
      begin
         for k:=0 to Tailer.ZSize - 1 do
         begin
            NewData[i,j,k]:=PackedVoxel;
         end;
      end;
   end;
   if Multiply then
   begin
      VectorPos[0]:=Max(VectorPos[0]*Tailer.XSize-1,0);
      VectorPos[1]:=Max(VectorPos[1]*Tailer.YSize-1,0);
      VectorPos[2]:=Max(VectorPos[2]*Tailer.ZSize-1,0);
   end;
   for i:=0 to Tailer.XSize - 1 do
   begin
      for j:=0 to Tailer.YSize - 1 do
      begin
         for k:=0 to Tailer.ZSize - 1 do
         begin
            a:=Round(i*VectorDir[0]+VectorPos[0]);
            b:=Round(j*VectorDir[1]+VectorPos[1]);
            c:=Round(k*VectorDir[2]+VectorPos[2]);
            //perform range checking
            if not (a<0) and not (b<0) and not (c<0) then
            begin
               if (a<Tailer.XSize) and (b<Tailer.YSize) and (c<Tailer.ZSize) then
                  NewData[a,b,c]:=Data[i,j,k];
            end;
         end;
      end;
   end;
   //That wasn't so hard...
   //now copy it back
   for i:=0 to Tailer.XSize - 1 do
   begin
      for j:=0 to Tailer.YSize - 1 do
      begin
         for k:=0 to Tailer.ZSize - 1 do
         begin
            Data[i,j,k] := NewData[i,j,k];
         end;
      end;
   end;
   // 1.3: Now, let's clear the memory
   for i := Low(NewData) to High(NewData) do
   begin
      for j := Low(NewData[i]) to High(NewData[i]) do
      begin
         SetLength(NewData[i,j],0);
      end;
      SetLength(NewData[i],0);
   end;
   SetLength(NewData,0);
end;}

{procedure TVoxelSection.ResizeBlowUp(Scale: Integer);
var
   NewData: array of array of array of TVoxelPacked; // as is 32-bit type, should be packed anyway
   i,j,k: Integer;
   Empty: TVoxel;
   PackedVoxel: TVoxelPacked;
begin
// prepare empty voxel
   with Empty do
   begin
      Color := 0;
      Normal := 0;
      Used := False;
   end;
   PackedVoxel := PackVoxel(Empty);

   //create new data matrix
   SetLength(NewData,Tailer.XSize*Scale,Tailer.YSize*Scale,Tailer.ZSize*Scale);
   //fill it with empty voxels
   for i:=0 to Tailer.XSize * Scale - 1 do
   begin
      for j:=0 to Tailer.YSize * Scale - 1 do
      begin
         for k:=0 to Tailer.ZSize * Scale - 1 do
         begin
            NewData[i,j,k]:=PackedVoxel;
         end;
      end;
   end;
   for i:=0 to Tailer.XSize * Scale - 1 do
   begin
      for j:=0 to Tailer.YSize * Scale - 1 do
      begin
         for k:=0 to Tailer.ZSize * Scale - 1 do
         begin
            NewData[i,j,k]:=Data[i div Scale,j div Scale,k div Scale];
         end;
      end;
   end;
   Resize(Tailer.XSize*Scale,Tailer.YSize*Scale,Tailer.ZSize*Scale);
   for i:=0 to Tailer.XSize - 1 do
   begin
      for j:=0 to Tailer.YSize - 1 do
      begin
         for k:=0 to Tailer.ZSize - 1 do
         begin
            Data[i,j,k]:=NewData[i,j,k];
         end;
      end;
   end;
   // 1.3: Now, let's clear the memory
   for i := Low(NewData) to High(NewData) do
   begin
      for j := Low(NewData[i]) to High(NewData[i]) do
      begin
         SetLength(NewData[i,j],0);
      end;
      SetLength(NewData[i],0);
   end;
   SetLength(NewData,0);
end;}

end.
